{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Solving Complex Tasks with A Sequence of Nested Chats\n",
    "\n",
    "This notebook shows how you can leverage **nested chats** to solve complex task with AG2. Nested chats is a sequence of chats created by a receiver agent after receiving a message from a sender agent and finished before the receiver agent replies to this message. Nested chats allow AG2 agents to use other agents as their inner monologue to accomplish tasks. This abstraction is powerful as it allows you to compose agents in rich ways. This notebook shows how you can nest a pretty complex sequence of chats among _inner_ agents inside an _outer_ agent.\n",
    "\n",
    "\\:\\:\\:info Requirements\n",
    "\n",
    "Install `autogen`:\n",
    "```bash\n",
    "pip install autogen\n",
    "```\n",
    "\n",
    "For more information, please refer to the [installation guide](https://docs.ag2.ai/latest/docs/user-guide/basic-concepts/installing-ag2).\n",
    "\n",
    "\\:\\:\\:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import autogen\n",
    "\n",
    "llm_config = autogen.LLMConfig.from_json(path=\"OAI_CONFIG_LIST\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\\:\\:\\:tip\n",
    "\n",
    "Learn more about the various ways to configure LLM endpoints [here](https://docs.ag2.ai/latest/docs/user-guide/basic-concepts/llm-configuration).\n",
    "\n",
    "\\:\\:\\:"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Example Task\n",
    "\n",
    "Suppose we want the agents to complete the following sequence of tasks:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tasks = [\n",
    "    \"\"\"On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\"\"\",\n",
    "    \"\"\"Make a pleasant joke about it.\"\"\",\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since the first task could be complex to solve, lets construct new agents that can serve as an inner monologue.\n",
    "\n",
    "### Step 1. Define Agents\n",
    "\n",
    "#### A Group Chat for Inner Monologue\n",
    "Below, we construct a group chat manager which manages an `inner_assistant` agent and an `inner_code_interpreter` agent. \n",
    "Later we will use this group chat inside another agent.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "inner_assistant = autogen.AssistantAgent(\n",
    "    \"Inner-assistant\",\n",
    "    llm_config=llm_config,\n",
    "    is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n",
    ")\n",
    "\n",
    "inner_code_interpreter = autogen.UserProxyAgent(\n",
    "    \"Inner-code-interpreter\",\n",
    "    human_input_mode=\"NEVER\",\n",
    "    code_execution_config={\n",
    "        \"work_dir\": \"coding\",\n",
    "        \"use_docker\": False,\n",
    "    },\n",
    "    default_auto_reply=\"\",\n",
    "    is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n",
    ")\n",
    "\n",
    "groupchat = autogen.GroupChat(\n",
    "    agents=[inner_assistant, inner_code_interpreter],\n",
    "    messages=[],\n",
    "    speaker_selection_method=\"round_robin\",  # With two agents, this is equivalent to a 1:1 conversation.\n",
    "    allow_repeat_speaker=False,\n",
    "    max_round=8,\n",
    ")\n",
    "\n",
    "manager = autogen.GroupChatManager(\n",
    "    groupchat=groupchat,\n",
    "    is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n",
    "    llm_config=llm_config,\n",
    "    code_execution_config={\n",
    "        \"work_dir\": \"coding\",\n",
    "        \"use_docker\": False,\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Inner- and Outer-Level Individual Agents\n",
    "\n",
    "Now we will construct a number of individual agents that will assume role of outer and inner agents."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assistant_1 = autogen.AssistantAgent(\n",
    "    name=\"Assistant_1\",\n",
    "    llm_config=llm_config,\n",
    ")\n",
    "\n",
    "assistant_2 = autogen.AssistantAgent(\n",
    "    name=\"Assistant_2\",\n",
    "    llm_config=llm_config,\n",
    ")\n",
    "\n",
    "writer = autogen.AssistantAgent(\n",
    "    name=\"Writer\",\n",
    "    llm_config=llm_config,\n",
    "    system_message=\"\"\"\n",
    "    You are a professional writer, known for\n",
    "    your insightful and engaging articles.\n",
    "    You transform complex concepts into compelling narratives.\n",
    "    \"\"\",\n",
    ")\n",
    "\n",
    "reviewer = autogen.AssistantAgent(\n",
    "    name=\"Reviewer\",\n",
    "    llm_config=llm_config,\n",
    "    system_message=\"\"\"\n",
    "    You are a compliance reviewer, known for your thoroughness and commitment to standards.\n",
    "    Your task is to scrutinize content for any harmful elements or regulatory violations, ensuring\n",
    "    all materials align with required guidelines.\n",
    "    You must review carefully, identify potential issues, and maintain the integrity of the organization.\n",
    "    Your role demands fairness, a deep understanding of regulations, and a focus on protecting against\n",
    "    harm while upholding a culture of responsibility.\n",
    "    \"\"\",\n",
    ")\n",
    "\n",
    "user = autogen.UserProxyAgent(\n",
    "    name=\"User\",\n",
    "    human_input_mode=\"NEVER\",\n",
    "    is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n",
    "    code_execution_config={\n",
    "        \"last_n_messages\": 1,\n",
    "        \"work_dir\": \"tasks\",\n",
    "        \"use_docker\": False,\n",
    "    },  # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Step 2: Orchestrate Nested Chats to Solve Tasks\n",
    "\n",
    "#### Outer Level\n",
    "In the following code block, at the outer level, we have communication between:\n",
    "\n",
    "- `user` - `assistant_1` for solving the first task, i.e., `tasks[0]`.\n",
    "- `user` - `assistant_2` for solving the second task, i.e., `tasks[1]`.\n",
    "\n",
    "#### Inner Level (Nested Chats)\n",
    "Since the first task is quite complicated, we created a sequence of _nested chats_ as the inner monologue of Assistant_1.\n",
    "\n",
    "1. `assistant_1` - `manager`: This chat intends to delegate the task received by Assistant_1 to the Manager to solve.\n",
    "\n",
    "2. `assistant_1` - `writer`: This chat takes the output from Nested Chat 1, i.e., Assistant_1 vs. Manager, and lets the Writer polish the content to make an engaging and nicely formatted blog post, which is realized through the writing_message function.\n",
    "\n",
    "3. `assistant_1` - `reviewer`: This chat takes the output from Nested Chat 2 and intends to let the Reviewer agent review the content from Nested Chat 2.\n",
    "\n",
    "4. `assistant_1` - `writer`: This chat takes the output from previous nested chats and intends to let the Writer agent finalize a blog post.\n",
    "\n",
    "The sequence of nested chats can be realized with the `register_nested_chats` function, which allows one to register one or a sequence of chats to a particular agent (in this example, the `assistant_1` agent).\n",
    "\n",
    "Information about the sequence of chats can be specified in the `chat_queue` argument of the `register_nested_chats` function. The following fields are especially useful:\n",
    "- `recipient` (required) specifies the nested agent;\n",
    "- `message` specifies what message to send to the nested recipient agent. In a sequence of nested chats, if the `message` field is not specified, we will use the last message the registering agent received as the initial message in the first chat and will skip any subsequent chat in the queue that does not have the `message` field. You can either provide a string or define a callable that returns a string.\n",
    "- `summary_method` decides what to get out of the nested chat. You can either select from existing options including `\"last_msg\"` and `\"reflection_with_llm\"`, or or define your own way on what to get from the nested chat with a Callable.\n",
    "- `max_turns` determines how many turns of conversation to have between the concerned agent pairs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def writing_message(recipient, messages, sender, config):\n",
    "    return f\"Polish the content to make an engaging and nicely formatted blog post. \\n\\n {recipient.chat_messages_for_summary(sender)[-1]['content']}\"\n",
    "\n",
    "\n",
    "nested_chat_queue = [\n",
    "    {\"recipient\": manager, \"summary_method\": \"reflection_with_llm\"},\n",
    "    {\"recipient\": writer, \"message\": writing_message, \"summary_method\": \"last_msg\", \"max_turns\": 1},\n",
    "    {\"recipient\": reviewer, \"message\": \"Review the content provided.\", \"summary_method\": \"last_msg\", \"max_turns\": 1},\n",
    "    {\"recipient\": writer, \"message\": writing_message, \"summary_method\": \"last_msg\", \"max_turns\": 1},\n",
    "]\n",
    "assistant_1.register_nested_chats(\n",
    "    nested_chat_queue,\n",
    "    trigger=user,\n",
    ")\n",
    "# user.initiate_chat(assistant, message=tasks[0], max_turns=1)\n",
    "\n",
    "res = user.initiate_chats([\n",
    "    {\"recipient\": assistant_1, \"message\": tasks[0], \"max_turns\": 1, \"summary_method\": \"last_msg\"},\n",
    "    {\"recipient\": assistant_2, \"message\": tasks[1]},\n",
    "])"
   ]
  }
 ],
 "metadata": {
  "front_matter": {
   "description": "Solve complex tasks with one or more sequence chats nested as inner monologue.",
   "tags": [
    "nested chat",
    "sequential chats",
    "orchestration"
   ]
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  },
  "vscode": {
   "interpreter": {
    "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
